using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Net;
using System.ServiceModel;
using System.ServiceModel.Security;
using System.Windows;
using System.Windows.Forms;
using DeSleeper.Client.Properties;
using DeSleeper.Client.Security;
using DeSleeper.Client.WakeOnLanService;
using NLog;

namespace DeSleeper.Client.WakeUp
{
    public class ProxyWakeUpDestination :  WakeUpDestination
    {
        private static readonly Logger _logger = LogManager.GetCurrentClassLogger();
        private static readonly List<WakeUpMethod> _methods = new List<WakeUpMethod> { new MacAddressWakeUpMethod(), new HostNameWakeUpMethod() };
        private static readonly List<WakeUpMethod> _frozenMethods =
            _methods.Select(method => (WakeUpMethod)method.GetCurrentValueAsFrozen()).ToList();

        protected override IEnumerable<WakeUpMethod> InnerMethods { get { return IsFrozen ? _frozenMethods : _methods; }}

        public ProxyWakeUpDestination()
        {
            SetValue(IdProperty, string.Format("Proxy/{0}", Guid.NewGuid()));
        }

        #region ProxyAddress Attached Property
        public static readonly DependencyProperty ProxyAddressProperty =
            DependencyProperty.Register("ProxyAddress", typeof(string), typeof(ProxyWakeUpDestination));

        public string ProxyAddress
        {
            get { return (string)GetValue(ProxyAddressProperty); }
            set { SetValue(ProxyAddressProperty, value); }
        }
        #endregion

        /// <summary>
        /// A description of the target used to identify it to the user.
        /// </summary>
        public override string Description
        {
            get
            {
                return string.Format(CultureInfo.CurrentUICulture,
                                     "Wake up a machine from a remote network via a proxy at {0}.", ProxyAddress);
            }
        }

        #region Id Dependency Property
        /// <summary>
        /// Id Dependency Property.
        /// </summary>
        public static readonly DependencyProperty IdProperty =
            DependencyProperty.Register(
                "Id",
                typeof (string),
                typeof (ProxyWakeUpDestination));

        /// <summary>
        /// Identifier used to identify target to code.
        /// </summary>
        public override string Id
        {
            get { return (string)GetValue(IdProperty); }
            set { SetValue(IdProperty, value); }
        }
        #endregion

        ///<summary>
        /// Creates a new instance to support cloning.
        ///</summary>
        protected override Freezable CreateInstanceCore()
        {
            return new ProxyWakeUpDestination();
        }

        public WakeUpServiceClient CreateClient()
        {
            var serviceClient = new WakeUpServiceClient();
            serviceClient.Endpoint.Binding = new WSHttpBinding("SecuredDefault");
            Uri address;
            if (!Uri.TryCreate(ProxyAddress, UriKind.Absolute, out address))
                if (!Uri.TryCreate(string.Format("http://{0}:8731/DeSleeper/WakeUpService", ProxyAddress),
                                   UriKind.Absolute, out address))
                    throw new InvalidOperationException("Proxy Address is not a valid URI or host name.");

            serviceClient.Endpoint.Address = new EndpointAddress(address);
            return serviceClient;
        }

        public override void WakeUp(TargetComputer targetComputer)
        {
            var result = tryCallService(targetComputer, null);
            if (result.AuthenticationFailed)
            {
                using (var form = new Form())
                using (var dialog = CredentialsDialog.CreateDialog(ProxyAddress))
                {
                    while (result.AuthenticationFailed)
                    {
                        form.TopLevel = true;
                        dialog.GetCredentials(form.Handle);
                        if (dialog.CredentialsState == CredentialState.Cancelled)
                            throw new SecurityAccessDeniedException(Resources.ErrorServiceAuthenticationFailed, result.InnerException);

                        result = tryCallService(targetComputer, dialog.Credentials);
                    }
                    dialog.ConfirmCredentials();
                }
            }
        }

        private ServiceResult tryCallService(TargetComputer targetComputer, NetworkCredential credential)
        {
            try
            {
                callService(targetComputer, credential);
                return ServiceResult.Success;
            }
            catch (SecurityNegotiationException e)
            {
                _logger.LogException(LogLevel.Warn, Resources.WarningWakeUpServiceFailedAuthentication, e);
                return ServiceResult.CreateAuthenticationFailure(e);
            }
            catch (MessageSecurityException e)
            {
                _logger.LogException(LogLevel.Warn, Resources.WarningWakeUpServiceFailedAuthentication, e);
                return ServiceResult.CreateAuthenticationFailure(e);
            }
            catch (SecurityAccessDeniedException e)
            {
                _logger.LogException(LogLevel.Warn, Resources.WarningWakeUpServiceFailedAuthentication, e);
                return ServiceResult.CreateAuthenticationFailure(e);
            }
        }

        private void callService(TargetComputer targetComputer, NetworkCredential credential)
        {
            var service = CreateClient();
            if (credential != null)
                service.ClientCredentials.Windows.ClientCredential = credential;

            targetComputer.WakeUp(service);
        }
    }
}